package com.lucifer.cloud.boot.blog.chat.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.lucifer.cloud.boot.blog.chat.advisor.ChatMessageAdvisor;
import com.lucifer.cloud.boot.blog.chat.advisor.LuciferVectorStoreChatMemoryAdvisor;
import com.lucifer.cloud.boot.blog.chat.common.MessageWrapper;
import com.lucifer.cloud.boot.blog.chat.database.MpConversationService;
import com.lucifer.cloud.boot.blog.chat.prompts.ChatPromptTemplate;
import com.lucifer.cloud.boot.blog.chat.service.ChatService;
import com.lucifer.cloud.boot.blog.chat.service.domain.bo.ChatConversationBo;
import com.lucifer.cloud.boot.blog.chat.service.domain.bo.ChatMemoryBo;
import com.lucifer.cloud.boot.blog.chat.service.domain.structured.TopN;
import com.lucifer.cloud.boot.blog.chat.service.response.ConversationVO;
import com.lucifer.cloud.boot.blog.chat.support.AliDashScopeSupportApi;
import com.lucifer.cloud.boot.blog.chat.support.ZhiPuAiSupportApi;
import com.lucifer.cloud.boot.blog.chat.util.IdUtil;
import com.lucifer.cloud.boot.blog.domin.bo.BaseConversation;
import com.lucifer.cloud.boot.blog.service.UploadService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.ai.chat.client.ChatClient;

import org.springframework.ai.chat.client.advisor.VectorStoreChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.id.RandomIdGenerator;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.rag.Query;
import org.springframework.ai.rag.preretrieval.query.transformation.CompressionQueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.QueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.RewriteQueryTransformer;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.Filter;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;


@Service
@RequiredArgsConstructor
public class ChatServiceImpl implements ChatService {

    final ChatClient chatClient;

    final ChatMemory chatMemory;

    private final MpConversationService conversationService;


    @Resource
    private ZhiPuAiChatModel zhiPuAiChatModel;


    @Resource
    private OpenAiChatModel openAiChatModel;

    @Resource
    private ZhiPuAiSupportApi zhiPuAiSupportApi;

    @Resource
    private AliDashScopeSupportApi aliDashScopeSupportApi;

    @Resource(name = "pgJdbcTemplate")
    private JdbcTemplate pgJdbcTemplate;


    @Resource
    private OllamaChatModel ollamaChatModel;

    @Resource(name = "pgVectorStore")
    private VectorStore vectorStore;


    @Resource(name = "chatMemoryVectorStore")
    private PgVectorStore chatMemoryVectorStore;

    @Value("${spring.ai.model}")
    private String model;


    @Resource(name = "zhiPuAiRestClient")
    private RestClient zhiPuAiRestClient;

    @Resource
    private UploadService uploadService;


    @Override
    public TopN topN(String message,String text) {
        Message systemMessage = ChatPromptTemplate.HotKeyPromptTemplate().createMessage(Map.of("text", text));
        UserMessage userMessage = new UserMessage(message);
        TopN topN = ChatClient.create(chatModel())
                .prompt(new Prompt(List.of(systemMessage, userMessage)))
                .call()
                .entity(TopN.class);
        return topN;
    }

    @Override
    public String chat(String conversationId, String message) {
        Prompt englishStudyAssistantPromptTemplate = ChatPromptTemplate.englishStudyAssistantPromptTemplate().create(Map.of("text", message));
        ChatClient.CallResponseSpec call = ChatClient.create(chatModel())
                .prompt(englishStudyAssistantPromptTemplate)
                //.advisors(VectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore).conversationId(conversationId).chatMemoryRetrieveSize(3).order(0).build())
                .call();
        return call.chatResponse().getResult().getOutput().getText();
    }

    @Override
    public String chatV2(String conversationId, String message) {
        FilterExpressionBuilder b = new FilterExpressionBuilder();
        Filter.Expression build = b.and(
                b.in("author", "john", "jill"),
                b.eq("article_type", "blog")).build();


        // 5: (year >= 2020 OR country == "BG") AND city NIN ["Sofia", "Plovdiv"]
        var exp5 = b.and(b.group(b.or(b.gte("year", 2020), b.eq("country", "BG"))), b.nin("city", "Sofia", "Plovdiv"));


        // 4: year >= 2020 OR country == "BG" AND city != "Sofia"
        var exp4 = b.and(b.or(b.gte("year", 2020), b.eq("country", "BG")), b.ne("city", "Sofia"));


        // 根据问题文本进行相似性搜索
        List<Document> listOfSimilarDocuments = vectorStore.similaritySearch(SearchRequest.builder()
                .query(message)
                .similarityThresholdAll()
                .filterExpression("name=='" + message + "' OR keyWordTop1=='" + message + "' OR keyWordTop2=='" + message + "' OR keyWordTop3=='" + message + "'")
                .topK(3)
                .build());
        // 将Document列表中每个元素的content内容进行拼接获得documents
        String documents = listOfSimilarDocuments.stream().map(Document::getText).collect(Collectors.joining());
        // 使用Spring AI 提供的模板方式构建SystemMessage对象
        Message systemMessage = ChatPromptTemplate.systemPromptTemplate().createMessage(Map.of("documents", documents));
        // 构建UserMessage对象
        UserMessage userMessage = new UserMessage(message);
        // 将Message列表一并发送给ChatGPT
        ChatClient.CallResponseSpec call = ChatClient.create(chatModel())
                .prompt(new Prompt(List.of(systemMessage, userMessage)))
                //.prompt()
                .advisors(
                        //new ChatMessageAdvisor(chatMemory, conversationId,3),
                        //QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().query(message).topK(1).build()).order(0).build(),
                        //PromptChatMemoryAdvisor.builder(chatMemory).systemTextAdvise(CHAT_SYSTEM_PROMPT).conversationId(conversationId).chatMemoryRetrieveSize(3).order(1).build(),
                        VectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore).conversationId(conversationId).chatMemoryRetrieveSize(3).order(0).build(),
                        ChatMessageAdvisor.builder(chatMemory).conversationId(conversationId).chatMemoryRetrieveSize(3).order(1).build()
                )
                //.user(message)
                //.tools(new DateTimeTools())
                .call();

        return call.chatResponse().getResult().getOutput().getText();
    }



    @Override
    public String uploadOss(String conversationId, String message, String chatType, MultipartFile file) {
        if(Objects.equals("ocr",chatType)){
            String ossImage = uploadService.simpleUpload("chat/image", file.getOriginalFilename(), file);
            return ossImage;
        }
        // 这是使用阿里
        if(Objects.equals("voice",chatType)){
            String ossVoice = uploadService.simpleUpload("chat/voice", file.getOriginalFilename(), file);
            return ossVoice;
        }
        return "";
    }



    @SneakyThrows
    @Override
    public String chatV3(String conversationId,String message,String chatType,MultipartFile file,String fileUrl) {

        if(!(Objects.equals("chat",chatType) || org.apache.commons.lang3.StringUtils.isBlank(chatType))){
            LuciferVectorStoreChatMemoryAdvisor storeChatMemoryAdvisor = LuciferVectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore).build();
            // voice 时不传message.
            if(org.apache.commons.lang3.StringUtils.isNotBlank(message)){
                UserMessage beforeUserMessage = new UserMessage(message);
                List<Document> beforeDocuments = storeChatMemoryAdvisor.toDocuments(List.of(beforeUserMessage), conversationId);
                chatMemoryVectorStore.write(beforeDocuments);
            }

            if(Objects.equals("ocr",chatType)){
                String ossImage = uploadService.simpleUpload("chat/image", file.getOriginalFilename(), file);
                UserMessage beforeOssImageUserMessage = new UserMessage(ossImage);
                List<Document> beforeOssImageDocuments = storeChatMemoryAdvisor.toDocuments(List.of(beforeOssImageUserMessage), conversationId);
                chatMemoryVectorStore.write(beforeOssImageDocuments);
            }

            // 这是使用阿里
            if(Objects.equals("voice",chatType)){
                String ossVoice = uploadService.simpleUpload("chat/voice", file.getOriginalFilename(), file);
                UserMessage beforeOssVoiceUserMessage = new UserMessage(ossVoice);
                List<Document> beforeVoiceDocuments = storeChatMemoryAdvisor.toDocuments(List.of(beforeOssVoiceUserMessage), conversationId);
                chatMemoryVectorStore.write(beforeVoiceDocuments);

                Method method = ReflectionUtils.findMethod(AliDashScopeSupportApi.class,chatType,String.class);
                Object invoke = method.invoke(aliDashScopeSupportApi, ossVoice);

                AssistantMessage afterAssistantMessage = new AssistantMessage(invoke.toString());
                List<Document> afterDocuments = storeChatMemoryAdvisor.toDocuments(List.of(afterAssistantMessage), conversationId);
                chatMemoryVectorStore.write(afterDocuments);
                return invoke.toString();

            }

            Method method = ReflectionUtils.findMethod(ZhiPuAiSupportApi.class, chatType,String.class,MultipartFile.class);
            Object invoke = method.invoke(zhiPuAiSupportApi, message,file);
            AssistantMessage afterAssistantMessage = new AssistantMessage(invoke.toString());
            List<Document> afterDocuments = storeChatMemoryAdvisor.toDocuments(List.of(afterAssistantMessage), conversationId);
            chatMemoryVectorStore.write(afterDocuments);
            return invoke.toString();
        }



        FilterExpressionBuilder b = new FilterExpressionBuilder();
        Filter.Expression filterExpression = b.eq("conversationId", conversationId).build();
        List<Document> listOfMemoryDocuments = chatMemoryVectorStore.similaritySearch(SearchRequest.builder()
                .query(message)
                .similarityThresholdAll()
                .topK(30)
                .filterExpression(filterExpression)
                .build());

        List<Message> history = Lists.newArrayList();
        Optional.ofNullable(listOfMemoryDocuments).orElse(Lists.newArrayList())
                .stream()
                .forEach(document -> {
                    String text = document.getText();
                    Map<String, Object> metadata = document.getMetadata();
                    if (metadata.get("messageType").equals("USER")) {
                        history.add(new UserMessage(text));
                    }
                    if (metadata.get("messageType").equals("ASSISTANT")) {
                        history.add(new AssistantMessage(text));
                    }
                });

        Query query = Query.builder()
                .text(message)
                .history(history)
                .build();

        QueryTransformer queryTransformer = CompressionQueryTransformer.builder()
                .chatClientBuilder(ChatClient.builder(chatModel()))
                .promptTemplate(ChatPromptTemplate.compressionQueryTransformerPromptTemplate())
                .build();


//        RewriteQueryTransformer rewriteQueryTransformer = RewriteQueryTransformer.builder()
//                .chatClientBuilder(ChatClient.builder(chatModel()))
//                .promptTemplate(ChatPromptTemplate.rewriteQueryTransformerPromptTemplate())
//                .build();

        Query transformedQuery = queryTransformer.transform(query);

        String text = transformedQuery.text();

        // 根据问题文本进行相似性搜索
        FilterExpressionBuilder db = new FilterExpressionBuilder();
        Filter.Expression expression = db.or(db.or(db.eq("name", message), db.eq("keyWordTop1", message)), db.or(db.eq("keyWordTop2", message), db.eq("keyWordTop3", message))).build();
        List<Document> listOfDataDocuments = vectorStore.similaritySearch(SearchRequest.builder()
                .query(message)
                .filterExpression(expression)
                .similarityThresholdAll()
                .topK(3)
                .build());
        // 将Document列表中每个元素的content内容进行拼接获得documents
        String documents = listOfDataDocuments.stream().map(Document::getText).collect(Collectors.joining());

        StringBuilder documentsBuilder = new StringBuilder();
        if (!StringUtils.isEmpty(documents)) {
            documentsBuilder.append(documents);
        } else {
            documentsBuilder.append(text);
        }
        // 使用Spring AI 提供的模板方式构建SystemMessage对象
        Message systemMessage = ChatPromptTemplate.systemPromptTemplate().createMessage(Map.of("documents", documentsBuilder.toString()));
        UserMessage userMessage = new UserMessage(message);

        // 将Message列表一并发送给ChatGPT
        ChatClient.CallResponseSpec call = ChatClient.create(chatModel())
                .prompt(new Prompt(List.of(systemMessage, userMessage)))
                .advisors(
                        LuciferVectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore).conversationId(conversationId).chatMemoryRetrieveSize(30).order(0).build()
                )
                .call();

        return call.chatResponse().getResult().getOutput().getText();
    }

    @Override
    public Map voice(String username, String localFilePath) {
        String voice = aliDashScopeSupportApi.voice(localFilePath);
        Map<@Nullable String, @Nullable String> map = Maps.newHashMap();
        map.put("data", voice);
        map.put("errorCode", "200");
        map.put("errorMessage", "success");
        System.out.println(map);
        return map;
    }

    @Override
    public String chatV4(String conversationId, String question, String data) {
        Message systemMessage = ChatPromptTemplate.liJiePromptTemplate().createMessage(Map.of("question", question,
                "data", data));
        UserMessage userMessage = new UserMessage(question);
        ChatClient.CallResponseSpec call = ChatClient.create(chatModel())
                .prompt(new Prompt(List.of(systemMessage, userMessage)))
                .advisors(
                        LuciferVectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore).conversationId(conversationId).chatMemoryRetrieveSize(10).order(0).build()
                )
                .call();
        return call.chatResponse().getResult().getOutput().getText();
    }

    @Override
    public Flux<ChatResponse> chatV5(String conversationId, String question, String data) {
        Message systemMessage = ChatPromptTemplate.liJiePromptTemplate().createMessage(Map.of("question", question,
                "data", data));
        UserMessage userMessage = new UserMessage(question);
        ChatClient.StreamResponseSpec stream = ChatClient.create(chatModel())
                .prompt(new Prompt(List.of(systemMessage, userMessage)))
                .advisors(
                        LuciferVectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore).conversationId(conversationId).chatMemoryRetrieveSize(10).order(0).build()
                )
                .stream();
        return stream.chatResponse();
    }


    private ChatModel chatModel() {
        if (Objects.equals("zhipuai", model)) {
            return zhiPuAiChatModel;
        } else if (Objects.equals("openai", model)) {
            return openAiChatModel;
        } else if (Objects.equals("ollama", model)) {
            return ollamaChatModel;
        } else {
            return zhiPuAiChatModel;
        }
    }


    @Override
    public ConversationVO create() {
        String conversationId = IdUtil.gen32UUID();
        String title = "New Chat";
        conversationService.save(BaseConversation.builder()
                .conversationId(conversationId)
                .title(title)
                .build());
        return ConversationVO.builder().conversationId(conversationId)
                .title(title)
                .build();
    }

    @Override
    public ConversationVO createV2(String conversationType,String tableName) {
        String title = "New Chat";
        if(Objects.equals("nl2sql", conversationType)){
            title = "NL2SQL";
        }
        String id = new RandomIdGenerator().generateId();
        pgJdbcTemplate.update("insert into chat_conversation(id,title,create_time,update_time,conversation_type,table_name) values(?,?,?,?,?,?)", id, title, new Date(), new Date(),conversationType,tableName);
        return ConversationVO.builder().conversationId(id)
                .title(title)
                .conversationType(conversationType)
                .tableName(tableName)
                .build();
    }

    @Override
    public void edit(String conversationId, String title) {
        if (!StringUtils.hasText(title)) {
            return;
        }
        conversationService.update(BaseConversation.builder().title(title).build(),
                new LambdaQueryWrapper<BaseConversation>().eq(BaseConversation::getConversationId, conversationId));
    }

    @Override
    public void editV2(String conversationId, String name) {
        if (!StringUtils.hasText(name)) {
            return;
        }
        pgJdbcTemplate.update("update chat_conversation set  title = '" + name + "' , update_time = '" + new Date() + "' where id = '" + conversationId + "'");
    }

    @Override
    public void setTableName(String conversationId, String tableName) {
        if (!StringUtils.hasText(tableName)) {
            return;
        }
        pgJdbcTemplate.update("update chat_conversation set  table_name = '" + tableName + "' , update_time = '" + new Date() + "' where id = '" + conversationId + "'");
    }

    @Override
    public List<ConversationVO> list() {
        return conversationService.list(new LambdaQueryWrapper<BaseConversation>().orderByAsc(BaseConversation::getCreatedTime))
                .stream().map(v -> ConversationVO.builder()
                        .conversationId(v.getConversationId())
                        .title(v.getTitle())
                        .build()).toList();
    }


    @Override
    public List<ConversationVO> listV2() {
        BeanPropertyRowMapper<ChatConversationBo> mapper = new BeanPropertyRowMapper<>(ChatConversationBo.class);
        List<ChatConversationBo> chatMemoryBoList = pgJdbcTemplate.query("select id,title,conversation_type,table_name from chat_conversation", mapper);
        return Optional.ofNullable(chatMemoryBoList).orElse(List.of())
                .stream()
                .map(chatConversationBo -> {
                    return ConversationVO.builder().conversationId(chatConversationBo.getId())
                            .title(chatConversationBo.getTitle())
                            .conversationType(chatConversationBo.getConversation_type())
                            .tableName(chatConversationBo.getTable_name())
                            .build();
                }).collect(Collectors.toList());
    }

    @Override
    public void delete(String conversationId) {
        conversationService.remove(
                new LambdaQueryWrapper<BaseConversation>().eq(BaseConversation::getConversationId, conversationId));
        Filter.Expression filterExpression = new Filter.Expression(
                Filter.ExpressionType.EQ,
                new Filter.Key("conversationId"),
                new Filter.Value(conversationId)
        );
        chatMemoryVectorStore.delete(filterExpression);
    }

    @Override
    public void deleteV2(String conversationId) {
        pgJdbcTemplate.update("delete from  chat_conversation where id = ?", conversationId);
    }

    @Override
    public ConversationVO get(String conversationId) {
        return conversationService.getOneOpt(
                        new LambdaQueryWrapper<BaseConversation>().eq(BaseConversation::getConversationId, conversationId))
                .map(v -> ConversationVO.builder().conversationId(v.getConversationId())
                        .title(v.getTitle())
                        .messages(MessageWrapper.fromConversationStr(v.getContent()))
                        .build()
                ).orElse(ConversationVO.builder().build());

    }

    @Override
    public ConversationVO getV2(String conversationId) {
        BeanPropertyRowMapper<ChatConversationBo> mapper = new BeanPropertyRowMapper<>(ChatConversationBo.class);
        List<ChatConversationBo> chatConversationBos = pgJdbcTemplate.query("select id,title,conversation_type,table_name from chat_conversation where id = '" + conversationId + "'", mapper);


        BeanPropertyRowMapper<ChatMemoryBo> chatMemoryBoBeanPropertyRowMapper = new BeanPropertyRowMapper<>(ChatMemoryBo.class);
        List<ChatMemoryBo> chatMemoryBoList = pgJdbcTemplate.query("select id,content,metadata as metadataJson,embedding from chat_memory where metadata->>'conversationId' = '" + conversationId + "' ORDER BY (metadata ->> 'created_at')::bigint", chatMemoryBoBeanPropertyRowMapper);


        BeanPropertyRowMapper<ChatMemoryBo> nl2SqlBoBeanPropertyRowMapper = new BeanPropertyRowMapper<>(ChatMemoryBo.class);
        List<ChatMemoryBo> nl2SqlBoList = pgJdbcTemplate.query("select id,content,metadata as metadataJson,embedding from nl2sql_store where metadata->>'conversationId' = '" + conversationId + "' and metadata->>'script_type' = 'DDL'", nl2SqlBoBeanPropertyRowMapper);

        ChatMemoryBo nl2SqlBo = nl2SqlBoList.isEmpty() ? new ChatMemoryBo() : nl2SqlBoList.get(0);
        nl2SqlBo = new ChatMemoryBo(nl2SqlBo);
        Map<String, Object> nl2SqlMetadata = nl2SqlBo.getMetadata();


        List<MessageWrapper> messages = Optional.ofNullable(chatMemoryBoList).orElse(Lists.newArrayList())
                .stream()
                .map(chatMemoryBo -> {
                    ChatMemoryBo memoryBo = new ChatMemoryBo(chatMemoryBo);
                    Map<String, Object> metadata = memoryBo.getMetadata();
                    MessageWrapper messageWrapper = new MessageWrapper();
                    messageWrapper.setContent(memoryBo.getContent());
                    messageWrapper.setType(MessageType.valueOf((String) metadata.get("messageType")));
                    return messageWrapper;
                }).collect(Collectors.toList());

        return ConversationVO.builder().conversationId(conversationId)
                .title(chatConversationBos.get(0).getTitle())
                .conversationType(chatConversationBos.get(0).getConversation_type())
                .tableName(chatConversationBos.get(0).getTable_name())
                .nl2sqlQuestion((String) nl2SqlMetadata.get("script_type"))
                .nl2sqlContent(nl2SqlBo.getContent())
                .nl2sqlPolicy((String) nl2SqlMetadata.get("script_type"))
                .messages(messages)
                .build();
    }


}
